package com.zbest.common.lang.base;

import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;

import com.zbest.common.lang.enums.BizCode;
import com.zbest.common.lang.util.CoreCommonUtils;

/**
 * 份额<br/>
 *
 * Created by wu.charles on 2015/1/19.
 */
public final class Quty implements Serializable, Comparable<Quty> {
    /**
     * 默认小数位数<br/>
     *
     */
    public static final int DEFAULT_SCALE = 2;

    /**
     * 四舍五入<br/>
     *
     */
    public static final int DEFAULT_ROUNDING_MODE = BigDecimal.ROUND_HALF_UP;

    /**
     * 份额值<br/>
     *
     */
    private long value = 0L;

    /**
     * 份额小数位数<br/>
     *
     */
    private int scale = -1;

    public Quty() {
        this(0, DEFAULT_SCALE);
    }

    public Quty(long value, int scale) {
        if(scale < 0) {
            CoreCommonUtils.raiseBizException(BizCode.PARAM_ERROR, "scale必须大于等于0");
        }

        this.value = value;

        this.scale = scale;
    }

    public Quty(BigDecimal value, int scale) {
        this(value, scale, DEFAULT_ROUNDING_MODE);
    }

    public Quty(BigDecimal value, int scale, int roundingMode) {
        this.scale = scale;

        final BigDecimal newValue = value.movePointRight(scale);
        this.value = newValue.setScale(0, roundingMode).longValue();
    }

    public long getValue() {
        return value;
    }

    public void setValue(long value) {
        this.value = value;
    }

    public int getScale() {
        return scale;
    }

    public BigDecimal toFloat() {
        return new BigDecimal(BigInteger.valueOf(value), scale);
    }

    @Override
    public int compareTo(Quty o) {
        return this.toFloat().compareTo(o.toFloat());
    }

    @Override
    public int hashCode() {
        return (int)(value ^ (value >>> 32));
    }

    @Override
    public boolean equals(Object obj) {
        if(this == obj) {
            return true;
        }

        if (obj instanceof Quty) {
            return this.toFloat().equals(((Quty)obj).toFloat());
        }

        return false;
    }

    @Override
    public String toString() {
        return toFloat().toString();
    }

    // ------------------------ + -

    /**
     * 两个quty相加，返回一个新的quty对象，而原来的quty都不会变化<br/>
     *
     * @param quty
     * @return
     */
    public Quty add(Quty  quty) {
        assertSameQuty(this, quty);

        return add(quty, DEFAULT_ROUNDING_MODE);
    }

    /**
     * 两个quty相加，返回一个新的quty对象，而原来的quty都不会变化<br/>
     *
     * @param quty
     * @param roundingMode
     * @return
     */
    public Quty add(Quty quty, int roundingMode) {
        assertSameQuty(this, quty);

        BigDecimal result = addOp(quty, roundingMode);

        return new Quty(result.longValue(), scale);
    }

    /**
     * 两个quty的加法操作<br/>
     *
     * @param quty
     * @param roundingMode
     * @return
     */
    private BigDecimal addOp(Quty quty, int roundingMode) {
        BigDecimal _this = this.toFloat();
        BigDecimal _other = quty.toFloat();

        BigDecimal result = _this.add(_other);
        result = result.movePointRight(scale);
        result = result.setScale(0, roundingMode);
        return result;
    }

    /**
     * 两个quty相加，被加数会改变<br/>
     *
     * @param quty
     * @return
     */
    public Quty addTo(Quty  quty) {
        assertSameQuty(this, quty);

        return addTo(quty, DEFAULT_ROUNDING_MODE);
    }

    /**
     * 两个quty相加，被加数会改变<br/>
     *
     * @param quty
     * @param roundingMode
     * @return
     */
    public Quty addTo(Quty quty, int roundingMode) {
        assertSameQuty(this, quty);

        BigDecimal result = addOp(quty, roundingMode);

        value = result.longValue();

        return this;
    }

    /**
     * 两个quty相减，返回一个新的quty对象，而原来的quty都不会变化<br/>
     *
     * @param quty
     */
    public Quty subtract(Quty  quty) {
        assertSameQuty(this, quty);

        return subtract(quty, DEFAULT_ROUNDING_MODE);
    }

    /**
     * 两个quty相减，返回一个新的quty对象，而原来的quty都不会变化<br/>
     *
     * @param quty
     * @param roundingMode
     */
    public Quty subtract(Quty quty, int roundingMode) {
        assertSameQuty(this, quty);

        BigDecimal result = subtractOp(quty, roundingMode);

        return new Quty(result.longValue(), scale);
    }

    /**
     * 两个quty相减，被减数会改变<br/>
     *
     * @param quty
     */
    public Quty subtractFrom(Quty  quty) {
        assertSameQuty(this, quty);

        return subtractFrom(quty, DEFAULT_ROUNDING_MODE);
    }

    /**
     * 两个quty相减，被减数会改变<br/>
     *
     * @param quty
     * @param roundingMode
     */
    public Quty subtractFrom(Quty quty, int roundingMode) {
        assertSameQuty(this, quty);

        BigDecimal result = subtractOp(quty, roundingMode);

        value = result.longValue();

        return this;
    }

    private BigDecimal subtractOp(Quty quty, int roundingMode) {
        BigDecimal _this = this.toFloat();
        BigDecimal _other = quty.toFloat();

        BigDecimal result = _this.subtract(_other);
        result = result.movePointRight(scale);
        result = result.setScale(0, roundingMode);

        return result;
    }

    // ------------------------ *

    /**
     * 两个quty相乘，返回一个新的quty实例，乘数和被乘数都不会变<br/>
     *
     * @param quty
     * @return
     */
    public Quty multiply(long quty) {
        return new Quty(value*quty, scale);
    }

    /**
     * 两个quty相乘，返回一个新的quty实例，乘数和被乘数都不会变<br/>
     *
     * @param quty
     * @return
     */
    public Quty multiply(double quty) {
        return new Quty(Math.round(value * quty), scale);
    }

    /**
     * 两个quty相乘，返回一个新的quty实例，乘数和被乘数都不会变<br/>
     *
     * @param quty
     * @return
     */
    public Quty multiply(BigDecimal quty) {
        return multiply(quty, DEFAULT_ROUNDING_MODE);
    }

    /**
     * 两个quty相乘，返回一个新的quty实例，乘数和被乘数都不会变<br/>
     *
     * @param quty
     * @return
     */
    public Quty multiply(BigDecimal quty, int roundingMode) {
        long value = multiplyOp(quty, roundingMode);

        return new Quty(value, scale);
    }

    private long multiplyOp(BigDecimal quty, int roundingMode) {
        BigDecimal newValue = BigDecimal.valueOf(value).multiply(quty);
        return newValue.setScale(0, roundingMode).longValue();
    }

    /**
     * 两个quty相乘, 被乘数会变<br/>
     *
     * @param quty
     * @return
     */
    public Quty multiplyBy(long quty) {
        value = value * quty;

        return this;
    }

    /**
     * 两个quty相乘, 被乘数会变<br/>
     *
     * @param quty
     * @return
     */
    public Quty multiplyBy(double quty) {
        value = Math.round(value * quty);

        return this;
    }

    /**
     * 两个quty相乘, 被乘数会变<br/>
     *
     * @param quty
     * @return
     */
    public Quty multiplyBy(BigDecimal quty) {
        return multiplyBy(quty, DEFAULT_ROUNDING_MODE);
    }

    /**
     * 两个quty相乘, 被乘数会变<br/>
     *
     * @param quty
     * @return
     */
    public Quty multiplyBy(BigDecimal quty, int roundingMode) {
        this.value = multiplyOp(quty, roundingMode);

        return this;
    }

    // -------------------------- 辅助函数
    private void assertSameQuty(Quty left, Quty right) {
        if(left.scale != right.scale) {
            throw new IllegalArgumentException("Quty scale mismatch.");
        }
    }
}
